package com.qijian.common.core.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Preconditions;
import com.qijian.common.annotation.JoinField;
import com.qijian.common.annotation.RollbackEntityField;
import com.qijian.common.annotation.RollbackField;
import com.qijian.common.constant.HttpStatus;
import com.qijian.common.core.domain.AjaxResult;
import com.qijian.common.core.domain.model.LoginUser;
import com.qijian.common.core.page.PageDomain;
import com.qijian.common.core.page.TableDataInfo;
import com.qijian.common.core.page.TableDataInfoModel;
import com.qijian.common.core.page.TableSupport;
import com.qijian.common.enums.ApprovalDescriptionEnum;
import com.qijian.common.exception.ServiceException;
import com.qijian.common.page.PageWith;
import com.qijian.common.utils.DateUtils;
import com.qijian.common.utils.ObjectUtils;
import com.qijian.common.utils.SecurityUtils;
import com.qijian.common.utils.StringUtils;
import com.qijian.common.utils.sql.SqlUtil;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

import java.beans.PropertyEditorSupport;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.qijian.common.utils.mybatis.WhereEntityTool.camelToUnderline;

/**
 * web层通用数据处理
 *
 * @author ruoyi
 */
public class BaseController {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 将前台传递过来的日期格式的字符串，自动转化为Date类型
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(DateUtils.parseDate(text));
            }
        });
    }

    /**
     * 设置请求分页数据
     */
    protected void startPage() {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize)) {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            PageHelper.startPage(pageNum, pageSize, orderBy);
        }
    }

    /**
     * 设置请求排序数据
     */
    protected void startOrderBy() {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) {
            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
            PageHelper.orderBy(orderBy);
        }
    }

    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected TableDataInfo getDataTable(List<?> list) {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(new PageInfo(list).getTotal());
        return rspData;
    }

    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected TableDataInfo getDataTable(IPage page) {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(page.getRecords());
        rspData.setTotal(page.getTotal());
        return rspData;
    }

    protected TableDataInfoModel getDataInfoModel(IPage page) {
        TableDataInfoModel rspData = new TableDataInfoModel();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(page.getRecords());
        rspData.setTotal(page.getTotal());
        return rspData;
    }

    /**
     * 将排序的列名清除空格，避免sql注入
     */
    protected void cleanOrderItem(List<OrderItem> orders) {
        orders.forEach(o -> o.setColumn(o.getColumn().replaceAll(" ", "")));
    }

    protected void initOrderItem(Class<?> clazz, List<OrderItem> orders) {
        Map<Integer, OrderItem> newOrders = new LinkedHashMap<>();

        cleanOrderItem(orders);
        Field[] fields = clazz.getDeclaredFields();
        TableName tableName = clazz.getAnnotation(TableName.class);
        String pre = tableName != null ? tableName.value() + '.' : "";
        for (int i = orders.size() - 1; i > -1; i--) {
            OrderItem o = orders.get(i);
            boolean c = false;
            for (Field field : fields) {
                if (field.getName().equals(o.getColumn())) {
                    String column = o.getColumn();
                    TableField tableField = field.getAnnotation(TableField.class);
                    if (tableField != null && StringUtils.isNotEmpty(tableField.value())) {
                        if (!tableField.exist()) {
                            column = tableField.value();
                        } else {
                            column = pre + tableField.value();
                        }
                    } else {
                        column = pre + camelToUnderline(column);
                    }
                    o.setColumn(column);
                    if (field.getType().equals(String.class)) {
                        OrderItem item = new OrderItem();
                        item.setColumn(column);
                        item.setAsc(o.isAsc());
                        o.setColumn("fristPinyin(" + column + ")");
                        newOrders.put(i, item);
                    }
                    c = true;
                    break;
                }
            }
            if (!c) {
                Field field = ReflectUtil.getField(clazz, o.getColumn());
                String column = pre + camelToUnderline(o.getColumn());
                o.setColumn(column);
                if (field != null && field.getType().equals(String.class)) {
                    OrderItem item = new OrderItem();
                    item.setColumn(o.getColumn());
                    item.setAsc(o.isAsc());
                    o.setColumn("fristPinyin(" + camelToUnderline(o.getColumn()) + ")");
                    newOrders.put(i, item);
                }
            }
        }

        // 再次排序，字符串首字母相同出现顺序不一致情况
        if (CollUtil.isNotEmpty(newOrders)) {
            newOrders.forEach(orders::add);
        }
    }

    /**
     * 需要在detail表中的关联主表字段上加@JoinField
     * 才能正确使用此方法
     * <p>
     * 只会获取clazz的字段，无法获取父类的字段，clazz需要为标注注解的基类
     * <p>
     * 方法会自动在新添加的details关联主表id
     *
     * @param id    主表的id
     * @param data  details数据
     * @param clazz details类
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static <T extends Model<?>> void cleanDetailsData(Long id, Collection<? extends T> data, Class<T> clazz) {
        //当明细为null时，判断为不做处理，如果需要清空已有详情，需要传size = 0 的 list
        if (data == null || id == null)
            return;
        Field joinField = null;
        Field idField = null;
        String idFieldName = null;
        String joinFieldName = null;
        try {
            T instance = clazz.newInstance();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                JoinField joinFieldAnn = field.getAnnotation(JoinField.class);
                if (joinFieldAnn != null) {
                    joinField = field;
                    joinField.setAccessible(true);
                    joinField.set(instance, id);
                    TableField tf = joinField.getAnnotation(TableField.class);
                    joinFieldName = (tf != null && !tf.value().equals("")) ? tf.value() : camelToUnderline(joinField.getName());
                    break;
                }
            }
            if (joinField == null) throw new UnsupportedOperationException("无法使用该方法：缺少关联字段注解,请使用@JoinField标注。");

            for (Field field : fields) {
                TableId tableIdAnn = field.getAnnotation(TableId.class);
                if (tableIdAnn != null || field.getName().equals("id")) {
                    idField = field;
                    idField.setAccessible(true);
                    TableField tf = idField.getAnnotation(TableField.class);
                    idFieldName = (tf != null && !tf.value().equals("")) ? tf.value() : camelToUnderline(idField.getName());
                    break;
                }
            }
            if (idField == null) throw new UnsupportedOperationException("无法使用该方法：无法识别主键,请使用@TableId标注。");

            List<Object> ids = new ArrayList<>();
            //根据关联字段field设置关联id
            //收集所有详情的id
            for (T d : data) {
                joinField.set(d, id);
                Object detailId = idField.get(d);
                if (ObjectUtils.isNotEmpty(detailId))
                    ids.add(detailId);
            }

            QueryWrapper wrapper = new QueryWrapper();
            wrapper.eq(joinFieldName, id);
            if (ids.size() > 0)
                wrapper.notIn(idFieldName, ids);
            instance.delete(wrapper);

//            @SuppressWarnings("unchecked")
//            List<T> details = (List<T>) instance.selectList(WhereEntityTool.invoke(instance));
//            if (details.size() > 0) {
//                List<T> willDelDetails = new ArrayList<>();
//                for (T d : details) {
//                    boolean isDel = true;
//                    for (T detail : data) {
//                        Object oldId = idField.get(d);
//                        Object nowId = idField.get(detail);
//                        if (nowId != null && nowId.equals(oldId)) {
//                            isDel = false;
//                            break;
//                        }
//                    }
//                    if (isDel) willDelDetails.add(d);
//                }
//                //删除需要删除的详情数据
//                if (willDelDetails.size() > 0) for (T willDelDetail : willDelDetails) willDelDetail.deleteById();
//            }

            if (CollectionUtil.isNotEmpty(data)) data.forEach(d -> d.insertOrUpdate());

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public static <T extends Model<?>> List<T> getIdEntityList(Class<T> clazz, Collection<Long> ids) {
        if (CollectionUtil.isEmpty(ids))
            return new ArrayList<>();
        try {
            Field[] fields = clazz.getDeclaredFields();
            String column = null;
            for (Field field : fields) {
                TableId idAnn = field.getAnnotation(TableId.class);
                if (idAnn != null || field.getName().equals("id")) {
                    TableField tableField = field.getAnnotation(TableField.class);
                    if (tableField != null)
                        column = tableField.value();
                    else
                        column = field.getName().equals("id") ? "id" : camelToUnderline(field.getName());
                    break;
                }
            }
            T instance = clazz.newInstance();
            QueryWrapper wrapper = new QueryWrapper();
            wrapper.in(column, ids);
            return instance.selectList(wrapper);

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    public static <T extends Model<?>> Map<Long, T> getIdEntityMap(Class<T> clazz, Collection<Long> ids) {
        if (CollectionUtil.isEmpty(ids))
            return new HashMap<>();
        Field[] fields = clazz.getDeclaredFields();
        Field idField = null;
        for (Field field : fields) {
            TableId idAnn = field.getAnnotation(TableId.class);
            if (idAnn != null || field.getName().equals("id")) {
                idField = field;
                idField.setAccessible(true);
                break;
            }
        }
        Field finalIdField = idField;
        return getIdEntityList(clazz, ids).stream()
                .collect(Collectors.toMap(d -> {
                    try {
                        return (Long) finalIdField.get(d);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        return null;
                    }
                }, a -> a));
    }

    /**
     * 单据反写上游字段
     * <p>
     * 会查询mainId的历史数据，用作扣除老数据，
     * 所以必须在新增删除明细之前执行该方法
     * 否则老数据已被删除，无法返还数量
     *
     * @param source  修改单据
     * @param target  上游单据
     * @param mainId  当前修改单据的主表id
     * @param newData 当前修改的详情
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static <S extends Model<?>, T extends Model<?>> void rollBackDataWithNew(
            Class<S> source, Class<T> target, Long mainId, List<? extends S> newData) {
        if (newData == null) newData = new ArrayList<>();
        List<S> oldData = new ArrayList<>();
        try {
            S s = source.newInstance();
            T t = target.newInstance();
            //如果mainId为null,则为新增，不需要查询全量旧数据
            if (mainId != null) {
                Field joinField = FieldUtils.getFieldsWithAnnotation(source, JoinField.class)[0];
                Preconditions.checkNotNull(joinField, "无法找到@JoinField关联字段");
                joinField.setAccessible(true);
                //全量旧数据
                joinField.set(s, mainId);
                oldData = s.selectList(new QueryWrapper(s));
            }
            //
            Field targetIdField = null;
            for (Field field : target.getDeclaredFields()) {
                TableId idAnn = field.getAnnotation(TableId.class);
                if (idAnn != null || field.getName().equals("id")) {
                    targetIdField = field;
                    targetIdField.setAccessible(true);
                    break;
                }
            }
            Preconditions.checkNotNull(targetIdField, "无法找到target的主键");
            /*查询target的历史数据，在历史数据的基础上累加*/
            //根据source里的@RollbackEntityField匹配target的主键查询历史数据
            Field sourceRollbackEntityField = findRollbackEntityFieldGroupValue(source);

            Set<Long> targetIds = new HashSet<>();
            //处理新数据和旧数据
            for (S n : newData) {
                Object id = sourceRollbackEntityField.get(n);
                if (id != null)
                    targetIds.add((long) id);
            }
            for (S o : oldData) {
                Object id = sourceRollbackEntityField.get(o);
                if (id != null)
                    targetIds.add((long) id);
            }
            //原本没有数据会报错，考虑到删除作废数据（作废数据内无明细），不报错直接退出
            if (targetIds.size() == 0)
                return;
//            Preconditions.checkArgument(targetIds.size() > 0, "无法关联target主键；无有效数据");
            //根据target的旧数据做累加
            Map<Long, T> targetEntity = getIdEntityMap(target, targetIds);


            //从来源和目标分别找到@RollbackField字段,并根据value分组
            Map<String, List<Field>> sourceRollBackField = findRollbackFieldGroupValue(source);
            Map<String, List<Field>> targetRollBackField = findRollbackFieldGroupValue(target);

            //处理s的old数据
            for (S o : oldData) {
                Object id = sourceRollbackEntityField.get(o);
                if (id == null)
                    continue;
                for (String sk : sourceRollBackField.keySet()) {
                    //只有s和t都存在该分组，才能够执行值传递
                    if (!targetRollBackField.containsKey(sk)) break;
                    //source的rollback只启用第一个，无法多对多，只能一对多
                    Field sRollbackField = sourceRollBackField.get(sk).get(0);
                    List<Field> tRollbackFields = targetRollBackField.get(sk);
                    //获取老数据
                    T old = targetEntity.get((long) id);
                    //减去s旧数据的值
                    for (Field tRollbackField : tRollbackFields) {
                        tRollbackField.set(old, new BigDecimal(String.valueOf(tRollbackField.get(old))).subtract(
                                new BigDecimal(String.valueOf(sRollbackField.get(o)))
                        ));
                    }
                }
            }
            //处理s的new数据
            for (S o : newData) {
                Object id = sourceRollbackEntityField.get(o);
                if (id == null)
                    continue;
                for (String sk : sourceRollBackField.keySet()) {
                    //只有s和t都存在该分组，才能够执行值传递
                    if (!targetRollBackField.containsKey(sk)) break;
                    //source的rollback只启用第一个，无法多对多，只能一对多
                    Field sRollbackField = sourceRollBackField.get(sk).get(0);
                    List<Field> tRollbackFields = targetRollBackField.get(sk);
                    //获取老数据
                    T old = targetEntity.get((long) id);
                    //累加
                    for (Field tRollbackField : tRollbackFields) {
                        tRollbackField.set(old, new BigDecimal(String.valueOf(tRollbackField.get(old))).add(
                                new BigDecimal(String.valueOf(sRollbackField.get(o)))
                        ));
                    }
                }
            }
            //精简更新字段
            for (T value : targetEntity.values()) {
                T temp = target.newInstance();
                targetIdField.set(temp, targetIdField.get(value));
                for (String k : targetRollBackField.keySet()) {
                    if (sourceRollBackField.containsKey(k)) {
                        List<Field> fields = targetRollBackField.get(k);
                        for (Field f : fields) {
                            f.set(temp, f.get(value));
                        }
                    }
                }
                temp.updateById();
            }

        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private static Map<String, List<Field>> findRollbackFieldGroupValue(Class<?> clazz) {
        Map<String, List<Field>> result = new HashMap<>();
        for (Field declaredField : clazz.getDeclaredFields()) {
            RollbackField rb = declaredField.getAnnotation(RollbackField.class);
            if (rb != null) {
                for (String s : rb.value()) {
                    if (!result.containsKey(s)) {
                        result.put(s, new ArrayList<>());
                    }
                    declaredField.setAccessible(true);
                    result.get(s).add(declaredField);
                }

            }
        }
        if (result.size() == 0) {
            throw new UnsupportedOperationException("无法找到@RollbackField");
        }
        return result;
    }

    private static Field findRollbackEntityFieldGroupValue(Class<?> clazz) {
        for (Field declaredField : clazz.getDeclaredFields()) {
            RollbackEntityField rb = declaredField.getAnnotation(RollbackEntityField.class);
            if (rb != null) {
                declaredField.setAccessible(true);
                return declaredField;
            }
        }
        throw new UnsupportedOperationException("无法找到@RollbackEntityField");
    }

    public static void copyItemFields(Object source, Object target) {
        try {
            String[] copyField = new String[]{
                    "itemId", "itemName", "itemCode", "itemAttr", "itemUnit", "itemUnitSup",
                    "quantity", "quantitySup",
                    "tax", "taxRate", "unitPrice", "taxUnitPrice", "totalAmount", "totalTaxAmount"
            };
            Class<?> sourceClazz = source.getClass();
            Class<?> targetClazz = target.getClass();
            Field[] sourceFields = sourceClazz.getDeclaredFields();
            Field[] targetFields = targetClazz.getDeclaredFields();
            for (String fieldStr : copyField) {
                if (Arrays.stream(sourceFields).anyMatch(f1 -> f1.getName().equals(fieldStr) && Arrays.stream(targetFields).anyMatch(f2 -> f2.getName().equals(fieldStr)))) {
                    targetClazz.getDeclaredField(fieldStr).set(target, sourceClazz.getDeclaredField(fieldStr).get(source));
                }
            }
        } catch (NoSuchFieldException | IllegalAccessException ex) {
            ex.printStackTrace();
        }
    }


    /**
     * 返回成功
     */
    public AjaxResult success() {
        return AjaxResult.success();
    }

    /**
     * 返回失败消息
     */
    public AjaxResult error() {
        return AjaxResult.error();
    }

    /**
     * 返回成功消息
     */
    public AjaxResult success(Object message) {
        return AjaxResult.success(message);
    }

    /**
     * 返回失败消息
     */
    public AjaxResult error(String message) {
        return AjaxResult.error(message);
    }

    /**
     * 返回警告消息
     */
    public AjaxResult warn(String message)
    {
        return AjaxResult.warn(message);
    }

    /**
     * 响应返回结果
     *
     * @param rows 影响行数
     * @return 操作结果
     */
    protected AjaxResult toAjax(int rows) {
        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
    }

    /**
     * 响应返回结果
     *
     * @param result 结果
     * @return 操作结果
     */
    protected AjaxResult toAjax(boolean result) {
        return result ? success() : error();
    }

    /**
     * 页面跳转
     */
    public String redirect(String url) {
        return StringUtils.format("redirect:{}", url);
    }

    /**
     * 获取用户缓存信息
     */
    public LoginUser getLoginUser() {
        return SecurityUtils.getLoginUser();
    }

    /**
     * 获取登录用户id
     */
    public Long getUserId() {
        return getLoginUser().getUserId();
    }

    /**
     * 获取登录部门id
     */
    public Long getDeptId() {
        return getLoginUser().getDeptId();
    }

    /**
     * 获取登录用户名
     */
    public String getUsername() {
        return getLoginUser().getUsername();
    }

    public void canApproved(Object obj) {
        if (ObjectUtil.isNull(obj)) {
            throw new ServiceException("E01:单据信息错误！");
        }
        ApprovalDescriptionEnum approval = (ApprovalDescriptionEnum) ReflectUtil.getFieldValue(obj, "approvalStatus");
        if (approval == ApprovalDescriptionEnum.C) {
            throw new ServiceException("E01:单据已审核！");
        } else if (approval != ApprovalDescriptionEnum.B) {
            throw new ServiceException("E01:单据未提交！");
        }
    }

    /**
     * 提交审核判断
     *
     * @param obj /
     */
    public void approval(Object obj) {
        if (ObjectUtil.isNull(obj)) {
            throw new ServiceException("E01:单据信息错误！");
        }
        ApprovalDescriptionEnum approval = (ApprovalDescriptionEnum) ReflectUtil.getFieldValue(obj, "approvalStatus");
        if (approval == ApprovalDescriptionEnum.B) {
            throw new ServiceException("E01:单据审核中！");
        } else if (approval == ApprovalDescriptionEnum.C) {
            throw new ServiceException("E01:单据已审核！");
        }
    }

    /**
     * 审核判断
     *
     * @param obj /
     */
    public void approved(Object obj) {
        if (ObjectUtil.isNull(obj)) {
            throw new ServiceException("E01:单据信息错误！");
        }
        ApprovalDescriptionEnum approval = (ApprovalDescriptionEnum) ReflectUtil.getFieldValue(obj, "approvalStatus");
        if (approval == ApprovalDescriptionEnum.C) {
            throw new ServiceException("E01:单据已审核！");
        }
    }

    /**
     * 反审判断
     *
     * @param obj /
     */
    public void deApproval(Object obj) {
        if (ObjectUtil.isNull(obj)) {
            throw new ServiceException("E01:单据信息错误！");
        }
        ApprovalDescriptionEnum approval = (ApprovalDescriptionEnum) ReflectUtil.getFieldValue(obj, "approvalStatus");
        if (approval == ApprovalDescriptionEnum.A || approval == ApprovalDescriptionEnum.D) {
            throw new ServiceException("E01:单据无需反审！");
        }
    }

    /**
     * 判断是否当前用户
     * @param userId 检测用户内码
     */
    public void checkCurrentUser(Long userId) {
        if (ObjectUtils.isNull(userId) || SecurityUtils.getUserId().compareTo(userId) != 0) {
            throw new ServiceException("E01:非所属用户无法操作");
        }
    }

    /**
     * 响应请求分页数据
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected TableDataInfo getDataTable(PageWith page) {
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(page.getRecords());
        rspData.setTotal(page.getTotal());
        rspData.setO(page.getO());
        return rspData;
    }
}
